package com.gary.shiro.config.shiro;

import com.gary.shiro.annotation.Cache;
import com.gary.shiro.dao.UserDAO;
import com.gary.shiro.po.Permission;
import com.gary.shiro.po.User;
import com.gary.shiro.service.PermissionService;
import com.gary.shiro.service.UserRoleService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    @Resource
    private UserDAO userDAO;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private PermissionService permissionService;

    @Autowired
    private UserRoleService userRoleService;

    @Value("${permission.key:gary.shiro.permission}")
    private String permissionKey;

    //realm的名字
    @Override
    public String getName() {
        return "ShiroRealm";
    }
		
    //认证(登录)
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //token 是执行登录的时候 传递过来的 里面包含用户输入的用户名和密码
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String userName = usernamePasswordToken.getUsername();
        //根据用户名去查用户表
        Example example = new Example(User.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("username", userName);
        User user = userDAO.selectOneByExample(example);
        if(user == null){
            throw new UnknownAccountException("用户名不存在");
        }
		//返回SimpleAuthenticationInfo 主要有三个参数 principal、credentials、realmName
        //principal 会传递给doGetAuthorizationInfo方法 credentials是数据库中查询出来的密码 realmName 直接调用
        //getName就可以
        return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
    }
  
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //principals 里面包含有doGetAuthenticationInfo传递过来的principal 即上面的user
        User user = (User)principals.iterator().next();
        if (user == null) {
            throw new AuthenticationException("未登录");
        }

        String sessionId = (String)SecurityUtils.getSubject().getSession().getId();
        Set<String> permissions = queryPermissions(sessionId, user.getId());

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permissions);
        //simpleAuthorizationInfo.setRoles(roles); todo 没有做角色的权限控制
        return simpleAuthorizationInfo;
    }

    @Cache
    private Set<String> queryPermissions(String sessionId, Long userId) {
        Set<String> permissionSet = new HashSet<>();
        String key = permissionKey + ":" + sessionId;
        try {
            List<Object> permissions = redisTemplate.opsForList().range(key, 0, -1);
            if (CollectionUtils.isEmpty(permissions)) {
                permissionSet = selectPermissions(userId);
                redisTemplate.opsForList().leftPushAll(key, permissionSet.toArray());
                redisTemplate.expire(key, 5, TimeUnit.MINUTES);
            } else {
                for (Object p : permissions) {
                    permissionSet.add((String)p);
                }
            }

        }catch (Exception e) {
            log.error("查询用户权限异常 seqId = {} 尝试从数据库中获取 ", "seq", e);
            permissionSet = selectPermissions(userId);
        }
        return permissionSet;
    }

    private Set<String> selectPermissions(Long userId) {
        Set<String> permissionSet = new HashSet<>();
        List<Long> roleIds = userRoleService.queryRoleIdsByUserId(userId);
        Set<Permission> set = permissionService.getPermissionByRoleIds(roleIds);
        set.forEach(permission -> {
            String p = permission.getPermission();
            permissionSet.add(p);
        });
        return permissionSet;
    }
}